Table of Contents
So continuing with the blog series of, W25Q128 SPI based flash memory\’s , in the previous blogs W25Q128JV SPI Flash Memory: Part1 | gettobyte W25Q128JV SPI Flash Memory: Part2 | gettobyte we have gone through the introduction and overview for W25Q128JV flash memory\’s. From this blog we are going to start with the Application and Device driver development of W25Q128JV IC. The Driver which i am going to develop in this blog will be generic can be used with any MCU, by just replacing the SPI API\’s. This application driver will be generic and simple one which will be having API\’s to perform basic Operation on this chip.
We will be creating the 2 files, header file and source file(.h &.c) for W25Q128JV Application driver. Header file(.h) will be having all the Macros, Typedefs, Enums, Structures and function declarations. Source file(.c) will be having all the function definitions and local variables to be used in the driver.
Header file (W25Q128JV.h)
First thing that we are going to do is define the Object like Macro\’s for all the registers of W25Q128JV in the header file(.h) of W25Q128JV.
Macros are widely used in Embedded Programming for referring the registers address with the acronym of the Register names, so that it is easy for developer/user to understand the code or using the API. Like, above if we want to read the JEDECID of the chip, instead of writing 0x9F in the Application code we can pass the Macro JEDECID.
(Though we are not going to use all the registers of W25Q128, as in this blog we are just going to make the driver for following features. The Application driver will be having API\’s for reading-writing the data, erasing the data, reading-writing of Status registers, reading JEDEC ID , chip erase and chip initialise.)
/* * w25q128jv.h * * Created on: 15-Apr-2021 * Author: kunal */ #define WriteEnable 0x06 #define WriteDisable 0x04 #define Dummybyte 0xA5 #define ReadSR1 0x05 #define WriteSR1 0x01 #define ReadSR2 0x35 //0x35: 00110101 #define WriteSR2 0x31 #define ReadSR3 0x15 #define WriteSR3 0x11 #define Write_Enab_for_Vol_status_regist 0x50 #define ReadData 0x03 #define WriteData 0x02 #define ReadDataFast 0x0B #define JEDECID 0x9F #define UinqueID 0x4B #define SectErase4KB 0x20 #define SectErase32KB 0x52 #define SectErase64KB 0xD8 #define chiperase 0xC7 #define reset1 0x66 #define reset2 0x99 #define read_addr1 0x020000 #define read_addr2 0x030000 #define read_addr3 0x040000 #define BUSY_BIT 0x01 #define WRITE_ENABLE_LATCH 0x02
Next thing in Header file will be the function definitions that would be used for interacting with the W25Q128JV flash memory\’s.
void W25_Reset (void); void WriteEnable_flash(); void W25_Read_Data(uint32_t addr, char block[], uint32_t sz); void W25_Write_Data(uint32_t addr, char block[], uint32_t sz); uint32_t W25_Read_ID(void); void W25_Ini(void); void erase_sector4KB(uint32_t addr); void erase_sector32KB(uint32_t addr); void erase_sector64KB(uint32_t addr); void chip_erase(); void Uinque_ID(uint8_t uinque[]); void WriteSR(uint8_t SR_address, uint8_t SR_data); uint8_t ReadSR(uint8_t SR_address); void WaitForWriteEnd(void);
Apart from Object like Macro\’s and Function definition\’s their would be 2 additional function like Macro\’s.
//For STM32 CUBEMX #define cs_set() HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET) #define cs_reset() HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET) //For STM32 BareMetal #define cs_set() GPIOA->ODR |= GPIO_ODR_ODR4; #define cs_reset() GPIOA->ODR &= ~GPIO_ODR_ODR4;
As we are going to interface the W25Q128JV via SPI peripheral to our MCU\’s, in which MCU would be the Master device and W25Q128JV would be slave device. And in SPI -> Chip Select/Chip Enable pin is used for selecting the slave. Thus these 2 Macro\’s would be used for selecting the slave before the SPI instructions are send ( by using the cs_set()) and then deselecting the slave after the SPI instructions( by using the cs_reset()).
Source file(W25q128JV.c)
This file would be having all the function declarations of the functions which are defined in (W25Q128JV.h). The 2 most important API\’s which will Send and Receive the SPI commands are:
void SPI1_Send (uint8_t *dt, uint16_t cnt) { HAL_SPI_Transmit (&hspi1, dt, cnt, 5000); } void SPI1_Recv (uint8_t *dt, uint16_t cnt) { HAL_SPI_Receive (&hspi1, dt, cnt, 5000); }
API\’s Explained for Device Driver of W25Q128JV:
- void SPI1_Send ()
This function is wrapper for transmitting the data via SPI. not be used directly in Application driver, but it will always be called by Other API\’s of the driver to send the command to W25Q via SPI.
It has 2 parameters:
1) uint8_t *dt –>pointer to store the data that will be transmitted from the Host MCU to W25Q128JV.
2) uint16_t cnt –> Variable that will be storing the size of data that has to be transmitted from MCU to W25Q128JV.
void SPI1_Send (uint8_t *dt, uint16_t cnt) { HAL_SPI_Transmit (&hspi1, dt, cnt, 5000); }
- void SPI1_Recv()
This function is wrapper for receiving the data via SPI. This API is also not used directly by the Application Driver, but will be used by the other API\’s of the driver for receivng the data from W25Q via SPI.
It also has 2 parameters:
- uint8_t *dt –> pointer to store the data that will be received from the W25Q128JV.
- uint16_t cnt –> variable that will be storing the size of data that has to be received.
void SPI1_Recv (uint8_t *dt, uint16_t cnt) { HAL_SPI_Receive (&hspi1, dt, cnt, 5000); }
- void W25_Reset(): W25Q SPI flash Ic\’s come in small package and they have limited number of the pins. Thus W25Q provides the software reset instruction feature. User/Developer can reset the W25Q by sending the specified instructions to W25Q via SPI. After reset the device will come to its default state and loose all volatile content. Enable reset – 0x66( reset 1 macro) and Reset – 0x99( reset 2 macro)are the instructions that has to be send for generating the software reset. These 2 instructions has to be send in sequence, as any other command after the Enable reset command( 0x66) apart from Reset(0x99) will disable the reset procedure. Once the reset command is accepted it woulfd take approx 30us to reset the W25Q IC.
void W25_Reset (void) { cs_reset(); tx_buf[0] = reset1; tx_buf[1] = reset2; SPI1_Send(tx_buf, 2); cs_set(); }
- void WriteEnable_flash(): In W25Q, before writing to any Page, Erasing any sector/block or performing full chip erase. We have to send the Write enable Instruction via SPI. So this function is also not used directly by the Application code, but will be used in functions that would be performing writing data or erasing the data on the chip.
void WriteEnable_flash()
{
cs_reset();
tx_buf[0] = WriteEnable;
SPI1_Send(tx_buf,1);
cs_set();}
- void W25_Read_Data(): This API will be directly used in the Application Code. This API will read the data, from the memory of the W25Q. For reading the data from the memory block of W25Q, we have to first send the Read data instruction(0x03) and then followed by the 24 bit memory address, from which data has to be read. As on the SPI bus we can send 8 bit of data at a time via MOSI pin to the slave,. So 24 bit memory address has to be broken up in 8 bit of data and are stored in the array indexes of 8 bit each.
This function has 3 parameters:
- uint32_t addr –> This parameter will be used to send the address from where data has to be read.
- uint8_t* data –> Pointer to store the data that will be received after the reading from the memory address : addr of the W25Q
- uint32_t sz –> Variable that would be storing the size of the data that has to be read.
void W25_Read_Data(uint32_t addr, uint8_t* data, uint32_t sz) { cs_reset(); tx_buf[0] = ReadData; tx_buf[1] = (addr >> 16) & 0xFF; tx_buf[2] = (addr >> 8) & 0xFF; tx_buf[3] = addr & 0xFF; SPI1_Send(tx_buf, 4); SPI1_Recv(data, sz); cs_set(); }
IMPORTANT –> Very comman problem comes that on reading the data from the W25Q memory, many a times 0xff value is returned. 95 percent of time, this happens because that has not been written on the memory address properly. Reason for that is insufficient power supply, connections are not done properly or damaged jumper wires.
- void W25_Write_Data(): This API will be also directly used in the Application Code. This API will write the data, onto the memory of the W25Q. For writing the data onto the memory block of W25Q, we have to first send the Write data instruction(0x03) and then followed by the 24 bit memory address, at which data has to be written. As on the SPI bus we can send 8 bit of data at a time via MOSI pin to the slave,. So 24 bit memory address has to be broken up in 8 bit of data and are stored in the array indexes of 8 bit each.
This function has 3 parameters:
- uint32_t addr –> This parameter will be used to send the address onto which data has to be written.
- uint8_t* data –> Pointer to point the data that has to be written on the W25Q
- uint32_t sz –> Variable that would be storing the size of the data that has to be read.
void W25_Write_Data(uint32_t addr, uint8_t* data, uint32_t sz) { WriteEnable_flash(); HAL_Delay(100); cs_reset(); tx_buf[0] = WriteData; tx_buf[1] = (addr >> 16) & 0xFF; tx_buf[2] = (addr >> 8) & 0xFF; tx_buf[3] = addr & 0xFF; SPI1_Send(tx_buf, 4); SPI1_Send(data, sz); cs_set(); }
- uint32_t W25_Read_ID(): This API will be used to read the JEDECID(0x9F) of the SPI flash memory, By sending the JEDECID instruction, we would get the 24 bit data from the W25Q, that would be 8 bit Manufacturer ID(MF7-MF0: 0xEF) and 16 bit Device ID(ID15-ID0: 0x4018)
uint32_t W25_Read_ID(void) { uint8_t dt[4]; tx_buf[0] = JEDECID; cs_reset(); SPI1_Send(tx_buf, 1); SPI1_Recv(dt,3); cs_set(); return (dt[0] << 16) | (dt[1] << 8) | dt[2]; }
- void W25_Ini()
void W25_Ini(void) { HAL_Delay(100); W25_Reset(); HAL_Delay(100); unsigned int id = W25_Read_ID(); }
- void erase_sector4KB(): In W25Q128 we can erase the memory of the chip at a memory blocks of 4KB, 32KB and 64KB. After performing the Sector Erase instruction, all memory within that sector is raised to the 1s(0xff). Write Enable instruction must be executed before performing any kind of Erase operations on the chip. Thus WriteEnable_flash() API would be used before sending the Erase sector instruction. For erasing the 4KB of Sector we have to send the SectErase4KB(0x20) instruction, followed by the 24 bit memory address of which data has to be erased. For checking that whether erase operation is performed successfully, read the memory address that has been erased and it should be 0xff.
void erase_sector4KB(uint32_t addr) { WriteEnable_flash(); HAL_Delay(100); cs_reset(); tx_buf[0] = SectErase4KB; tx_buf[1] = (addr >> 16) & 0xFF; tx_buf[2] = (addr >> 8) & 0xFF; tx_buf[3] = addr & 0xFF; SPI1_Send(tx_buf,4); cs_set(); }
- void erase_sector32KB(): For erasing the 32KB of Sector we have to send the SectErase4KB(0x20) instruction, followed by the 24 bit memory address of which data has to be erased. For checking that whether erase operation is performed successfully, read the memory address that has been erased and it should be 0xff.
void erase_sector32KB(uint32_t addr) { WriteEnable_flash(); cs_reset(); tx_buf[0] = SectErase32KB; tx_buf[1] = (addr >> 16) & 0xFF; tx_buf[2] = (addr >> 8) & 0xFF; tx_buf[3] = addr & 0xFF; SPI1_Send(tx_buf,4); cs_set(); }
- void erase_sector64KB(): For erasing the 64KB of Sector we have to send the SectErase4KB(0x20) instruction, followed by the 24 bit memory address of which data has to be erased. For checking that whether erase operation is performed successfully, read the memory address that has been erased and it should be 0xff.
void erase_sector64KB(uint32_t addr) { WriteEnable_flash(); cs_reset(); tx_buf[0] = SectErase64KB; tx_buf[1] = (addr >> 16) & 0xFF; tx_buf[2] = (addr >> 8) & 0xFF; tx_buf[3] = addr & 0xFF; SPI1_Send(tx_buf,4); cs_set(); }
- void chip_erase(): We can also erase full memory of the chip, by sending the Chiperase instruction: (0xC7). After the chip erase, the memory of the device is set to 1s(0xff). A write Enable instruction must be executed before performing the chip erase.
void chip_erase() { WriteEnable_flash(); cs_reset(); tx_buf[0] = chiperase; SPI1_Send(tx_buf,1); cs_set(); }
- void WriteSR(): Their are status and configuration registers in W25Q, as mentioned in my previous blog. So to Write onto the status registers for configuring the W25Q128, we have to send the Write Status Register instruction to the IC via SPI. For writing on SR1 macro and instruction is (WriteSR1: 0x01), SR2(WriteSR2: 0x31) and SR3(WriteSR3:0x11). As we are performing the write operation on Status register, so WriteEnable instruction has to be send before it. After sending the Write Status Register instruction, send the 8 bit data that has to be written on the status register. Data that has to be send, is configured according to the bits of the corresponding Status Register as mentioned in this blog.
This function has 2 parameters:
- uint8_t SR_address -> This parameter will be used to send the address of the status register that has to be written
- uint8_t SR_data -> This parameter will be storing the 8 bit data that has to be written in corresponding Status Register. This parameter can eb send either in hexadecimal format or binary format.
void WriteSR(uint8_t SR_address, uint8_t SR_data) { WriteEnable_flash(); cs_reset(); tx_buf[0] = SR_address; tx_buf[1] = SR_data; SPI1_Send(tx_buf,2); cs_set(); }
- uint8_t ReadSR(): Their are status and configuration registers in W25Q, as mentioned in my previous blog. So to Read onto the status registers for configuring the W25Q128, we have to send the Read Status Register instruction to the IC via SPI. For Reading the SR1 macro and instruction is (ReadSR1: 0x05), SR2(ReadSR2: 0x35) and SR3(ReadS1R3:0x15). After sending the Read Status Register instruction, we will receive the 8 bit Data that has been configured/written onto that Status Register.
This function has 2 parameters:
- uint8_t SR_address -> This parameter will be used to send the address of the status register that has to be Read
- uint8_t SR_data -> This parameter will be storing the data that is been written onto that status Register.
uint8_t ReadSR(uint8_t SR_address) { uint8_t RSR[1] = {0}; cs_reset(); tx_buf[0] = SR_address; SPI1_Send(tx_buf,1); SPI1_Recv(RSR,1); cs_set(); return RSR[0]; }
- void WaitForWriteEnd()
void WaitForWriteEnd(void) { uint8_t StatusRegist1[1] = {0}; cs_reset(); tx_buf[0] = ReadSR1; SPI1_Send(tx_buf,1); SPI1_Recv(StatusRegist1,1); do { tx_buf[0] = Dummybyte; SPI1_Send(tx_buf,1); SPI1_Recv(StatusRegist1,1); } while ((StatusRegist1[0] & 0x01) == 0x01); cs_set(); }